问题

我们到底应该如何测试?

TDD的想法是非常美好的;单元测试、集成测试、系统测试、验收测试等构成的测试金字塔是非常美好的;通过测试,尽早地将问题暴露出来的想法也是非常美好的。但测试带来的收益和成本应该如何衡量呢?大团队应该如何测试?两三个人的小团队又应该如何测试?

如果你所在的团队已经有明确的测试规范,那不用操心这些问题;但如果是专注业务的小团队,各方面尚未有明确规范,测试应该做到什么程度,会是一个值得思考的问题。

一点实践

伊始,对测试算是一知半解,主要认知还来自主观认识,知道单元测试、集成测试基本概念,大致了解JUnit、Mock等工具或概念。于是想要系统了解,去看书、去阅读手册,了解了TDD,对一个项目从头到尾添加了单元测试,耗费两周,得到了数千行测试代码。但在后续开发中,还是没能坚持同步添加新功能的测试代码。分析了一下,有如下几点原因

  • 测试太细:controller层单独测试请求参数验证逻辑、service层针对大部分方法都添加了测试、repository层针对每个表、每个方法、每个约束条件都写了测试。
  • 测试代码维护不方便:由于采用了分层结构,且service依赖dao层,也可能自己相互依赖,还有被调用方法等,存在带量的mock,阅读时违反直觉(我怎么知道你这个业务方法里有哪些依赖,这就还得去研究业务方法,但好的测试不应该是这样)。

汲取教训,很多地方其实根本不用单元测试覆盖,以集成测试+关键逻辑单元测试的方法才是最好的。但矫枉过正,这次甚至不想要写测试代码就能把集成测试这块搞定了,期望的效果是:用文件(excel、数据库或json文件)将接口地址、输入、预期输出等进行声明,代码自动生成测试。但实际操作下来可行性不高,接口地址、输入等不变量好书,但对输出的验证是个麻烦:确切地直接匹配、验证某些字段存在、验证某些字段的值、只验证状态码;且有时需要接口调用之间有依赖关系,以便测试一个完整的使用case。于是本该简化的集成测试声明,有需要为了这些个性化需求订一套规则,这无疑又是在造轮子,万万是不可取的。

仔细想想,我就是在寻找针对测试的银弹,但世界上没有银弹,所以这注定失败。取而代之,最终我还是使用MockMvc进行集成测试,不同的是,我将它整理得更加易读。

一点思考

我在测试上做的这些事情,总归可以用一个问题来概括:业务项目应该如何测试?我的想法是,业务无非几点:路由、数据、业务逻辑。可按照如下逻辑写测试

  • 主要业务场景的集成测试,这部分使用@SpringBootTest,启动整个项目,可以用EmbeddedDatabase作数据库,使得测试时数据变现可预期。由于@SpringBootTest启动非常耗时,因此所有集成测试都写在一个类中。

    这一步的主要作用是验证代码的修改对主要功能没有影响。

  • 针对controller层的参数验证测试,使用@WebMvcTest单独启动controller层。

    这一步的主要作用是确保API各种非法数据能够被正确处理,从入口上拒绝脏数据。

  • 针对核心逻辑写传统意义上的单元测试。核心逻辑尽量写成无状态函数,方便测试也方便阅读。

  • 其它部分:自由发挥。

    前三部分是产出投入比最高的部分,是必须的,也肯定不会亏。这一步性价比相对低很多,写不写完全看自己。

PS:没有测试的心,再好再简单的测试工具也不会好用。相反,如果你想要写测试,总能写出方便阅读又有效的测试代码。

一点想法

上面仅仅保证了代码本身没有问题,我们再加上部署呢?

从项目维护角度来说,可用性非常重要,如何确保每次CD之后都是可用的?我的想法是:将冒烟测试加入CI/CD流程,CD灰度发布,对灰度部分进行冒烟测试。通过则全量更新,否则回滚并报错。冒烟测试按优先级从高到低应包含如下

  • 测试健康检查接口:证明可达性没有问题
  • 调用需要使用外部服务的接口:证明依赖服务没有问题
  • 调用主要业务case接口:证明主要业务没有问题

至此,我们构成了一个相对完整且安全的开发-发布链,能够满足大部分需求。

留言

2022-05-29

⬆︎TOP